/*****************************************************************************
 *   Copyright 2005 Tim A Wang
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 ****************************************************************************/
package com.jb.web.session;

import com.jb.faces.component.UIComponent;
import com.jb.framework.ModelBean;
import com.jb.framework.PersistentManager;
import com.jb.framework.Registry;
import com.jb.framework.Scope;
import com.jb.framework.pm.XMLPersistentManager;
import com.jb.security.Authenticator;
import com.jb.security.DummySecurityManager;
import com.jb.security.SecurityManager;
import com.jb.web.application.ApplicationFactoryImpl;
import com.jb.web.application.ApplicationImpl;
import com.jb.web.application.BrowserInfo;
import com.jb.web.application.PageSecurityException;
import com.jb.web.application.RootContainer;
import com.jb.web.application.configuration.Config;
import com.jb.web.application.configuration.ManagedBeanDef;
import com.jb.web.bean.Page;
import com.jb.web.bean.UIComponentImpl;
import com.jb.web.context.WebContext;
import com.jb.web.model.ApplicationConfig;

import java.io.File;
import java.io.Serializable;

import java.text.DateFormat;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * This is user session wrapped arround servlet session. Session can be serialized if it is needed.
 */
public class Session extends ModelBean implements HttpSessionBindingListener, Serializable, Scope{
    static final long serialVersionUID = 123123L;

    static Hashtable cachedLoaders = new Hashtable();    
    protected static Log log = LogFactory.getLog(Session.class.getName());
    private Config config = Config.getInstance();
    public static String WIZARD = "com.jb.wizard";
    //
    //check if the session user has permission to 
    //access the page
    //
     
    static final private String USERNAME = "username";
    static final private String HASHKEY_PREFIX = "region";
    static final private String LOGGED_IN = "loggedIn";
    
    public final int SESSION_EXPIRED = 2;
    public final int SESSION_INVALID = 0;
    public final int SESSION_VALID = 1;
    
    private transient Page pageInDesign;    
    //per session toolbar
    transient Page toolBar;
    private transient ApplicationImpl applicationImpl;

    //
    // this field to indicate currently if the request
    // is allowed to customizable. 
    //
    boolean customizable = false;

    //switch indicating whether to show property inspector
    boolean showMB = true;
    //browser info
    private transient BrowserInfo browserInfo;

    //place holder for the jb application. This is JVM signleton
    private transient ApplicationConfig application;

    //maintaining session level history of the page visited
    private transient ArrayList onLoginListeners = new ArrayList();
    private transient ArrayList onLogoutListeners = new ArrayList();

    //Error message that between requests
    //holding for cut or copied component reference
    transient private Clipboard clipboard = new Clipboard();

    //session variables
    transient private HashMap attributes = new HashMap();
    transient private Map <String, Page> pageCache = new HashMap<String, Page>();

    //default locale
    private transient Locale locale = Locale.getDefault();

    //
    private SessionUser contentOwner;

    //default session life span
    //this is made instance level so that can be personalized.
    //user holder
    private transient SessionUser sessionUser;

    //session level default application
    private String applicationName;

    //component currently selected by the user
    private transient  UIComponent selected;

    //Currently lifecycle context for the request.
    private transient WebContext context;
    transient String deniedPage;
    //
    //this field is set just before the page is return to the request
    //forms can use this field to go back to where it comes from
    //see registration form for more details
    //
    private String pageFrom;

    //if the session is authenticated
    private boolean bAuthenticated = false;

    //allow session drag and drop
    private boolean draggable = false;

    //indicate the current runmode of the session, false as running in design mode
    private boolean runTime = true;
    private int sessionState = SESSION_INVALID;

    //type of last visit 
    private long dateVisitedLastTime;

    /**
     * Default constructor
     */
    public Session() {
        super();
        init();
    }

    /**
     * Returns current application
     *
     * @return the application.
     */
    public ApplicationConfig getApplication() {
        if (application == null) {
            setApplicationName(applicationName);
        }
        return application;
    }

    /**
     * Assigns current application.
     *
     * @param applId new application id.
     */
    public void setApplicationName(String applId) {
        if (applicationName == null) {
            applicationName = config.getDefaultApplication();
        }

        if (!applicationName.equals(applId)) {
            ApplicationConfig appl = (ApplicationConfig)ApplicationConfig.findById(ApplicationConfig.class, applId);

            if (appl != null) {
                this.application = appl;
                this.applicationName = applId;
                pageCache.clear();
                return;
            }
        }
    }

    /**
     * Returns current application name
     *
     * @return Application Name
     */
    public String getApplicationName() {
        if (applicationName == null) {
            applicationName = config.getDefaultApplication();
        }
        return applicationName;
    }

    /**
     * Returns a list of applications that are allowed for this session user.
     *
     * @return list of applications.
     */
    public List getApplications() {
        return config.getApplications(getContext());
    }

    public Object getAttribute(String key) {
        Object cacheObject = attributes.get(key);

        if (log.isDebugEnabled()) {
            log.debug("Cache object retrieved " + key);
        }

        return cacheObject;
    }

    /**
     * Tests if the session has an authenticated user.
     *
     * @return true if it does.
     */
    public boolean isAuthenticated() {
        return bAuthenticated;
    }

    /**
     * Sets to new contentOwner. A contentOwner is the user whose page the current
     * account user is trying to visit. The personalization of this page must
     * be ContentOwner's rather the account users.
     *
     * @param userAccount DOCUMENT ME!
     */
    public void setContentOwner(SessionUser userAccount) {
        contentOwner = userAccount;
    }

    /**
     * Returns the current contentOwner
     *
     * @return contentOwner, if null, return session user
     */
    public SessionUser getContentOwner() {
        return ((contentOwner == null) || contentOwner.equals(sessionUser)) ? sessionUser : contentOwner;
    }


    /**
     * Returns contentOwner's personal web top. This information is used to locate
     * blog content as well as personalization information. If contentOwner is not
     * assigned, same information of the current account user is returned.
     * @return absolute location of contentOwner's web top.
     */
    public String getOwnerContentTop() {
        if ((contentOwner != null)) {
            return config.getUserContentTop() + File.separatorChar + contentOwner.getId();
        } else {
            return config.getUserContentTop() + File.separatorChar + sessionUser.getId();
        }
    }

    /**
     * Sets to new contentOwner. A contentOwner is the user whose blog page the current
     * account user is trying to visit. The personalization of this page must be
     * ContentOwner's rather the account users.   This method is called by event handler
     * of the blog page.
     *
     * @param bloggerName user name of the contentOwner.
     */
    public void setContentOwnerName(String bloggerName) {
        if ((contentOwner == null) || !contentOwner.getId().equals(bloggerName)) {
            if (bloggerName.equals(sessionUser.getId())) {
                contentOwner = sessionUser;
            } else {
                contentOwner = getSecurityManager().findUser(this.context, bloggerName);
            }
        }
    }

    public Clipboard getClipboard() {
        return clipboard;
    }

    public void setContext(WebContext context) {
        this.context = context;
    }

    public WebContext getContext() {
        return context;
    }

    public void setCustomizable(boolean customizable) {
        this.customizable = customizable;
    }

    public boolean isCustomizable() {
        return customizable;
    }

    public String getDate() {
        return DateFormat.getDateInstance().format(Calendar.getInstance(locale).getTime());
    }
    
    /**
     * Returns an UI component by ID for the page currently in design.
     *
     * @param componentId id of the component to be located
     *
     * @return UI Component found.
     */
    public synchronized UIComponentImpl getDesignComponentById(String componentId) {
        UIComponentImpl comp = null;
        if(componentId==null)
            return null;
        if (pageInDesign != null) {
            if (componentId.equals(pageInDesign.getId())) {
                comp = pageInDesign;
            } else {
                comp = (UIComponentImpl) pageInDesign.getDescendantById(componentId);
            }
        }

        if (comp == null) {
            Map tempMap = new HashMap();
            tempMap.putAll(pageCache);
            Iterator iter = tempMap.values().iterator();

            while (iter.hasNext()) {
                Page page = (Page) iter.next();

                if (page.getId().equals(componentId)) {
                    comp = page;

                    break;
                }

                comp = (UIComponentImpl) page.getDescendantById(componentId);

                if (comp != null) {
                    break;
                }
            }
        }
        return comp;
    }

    /**
     * Toggles DND flag. If it is disabled, drag and drop will not be allowed.
     *
     * @param draggable true to allow float panel being drag and droped.
     */
    public void setDraggable(boolean draggable) {
        this.draggable = draggable;
    }

    /**
     * Returns if DND is allowed
     *
     * @return DOCUMENT ME!
     */
    public boolean isDraggable() {
        return draggable && (context != null) && !context.isDeployed() && !this.isRunTime();
    }

    //return all the messages that registerred for this session, per request base

    /**
     * Return errors that assigned to this session.
     * @return error message.
     */
    public String getError() {
        return (context != null) ? context.getErrorMessage(this) : null;
    }

    public String getLang() {
        return locale.getLanguage();
    }

    /**
     * Return client session locale
     *
     * @return
     */
    public Locale getLocale() {
        return locale;
    }
    private synchronized PersistentManager _getPersistentManager() {
        
        PersistentManager pm = (PersistentManager)cachedLoaders.get(getApplicationName());

        if (pm != null) {
            return pm;
        }

        pm = applicationImpl.getApplicationPM(this.context);
        cachedLoaders.put(getApplicationName(), pm);

        return pm;
    }
    
    /**
     * Returns personal persistent manager
     *
     * @return
     */
    
    public synchronized PersistentManager getPersistentManager() {
/*        if(this.isRunTime())
            return getPersonalPM();
        else*/
        return _getPersistentManager();
    }    
    XMLPersistentManager personalManager;
    /**
     * Overrides the super class method to return persistent manager for
     * personalization
     * @return
     */
    public PersistentManager getPersonalPM() {
        
        if(personalManager!=null)
            return personalManager;
        
        String userTop = getPersonalContentTop();
        String ownerTop = getOwnerContentTop();
        
        if (!userTop.equals(ownerTop)) {
           personalManager = new XMLPersistentManager(_getPersistentManager() , ownerTop);
        } else {
           personalManager = new XMLPersistentManager(_getPersistentManager() , userTop);
        }
        
        return personalManager;
    }

    /**
     * Returns a page by name.
     *
     * <p>
     * This operation does page level security check before returning the page.
     * </p>
     *
     * <p>
     * Pages might be cached at session for performance reasons, but an
     * application should not depend on this feature in its implemenation.
     * </p>
     *
     * @param context WebContext to operate in
     * @param name name of the page
     *
     * @return page found by the name.
     */
    public synchronized Page getPage(WebContext context, String name, boolean embedded)
        throws PageSecurityException {
        if (log.isDebugEnabled()) {
            log.debug("Start getPage " + getClass().getName());
        }
        //if deniedPage is not null, meaning an attempt to access
        //this page with out login. Once login in, we
        //want to forward to this page
        //
        if(deniedPage!=null && deniedPage.equals(name))
            deniedPage=null;
        //application level component
        //construct new page, this is a lazy loading thing,
        //and only one per page for
        //entire application
        if (ApplicationImpl.TOOL_BAR.equals(name)) {
            if(log.isDebugEnabled())
                log.debug("Tool page is returned.");
            return getToolBar(context);
        }

        Page cachedPage = null;

        try {
            //that is the template, and should be cached
            cachedPage = (Page) RootContainer.findBeanz(context, name, Page.class);
            //if page is personalizable, load personal page
            if(cachedPage!=null && cachedPage.isPersonalizable()) {
                Page personalPage =(Page) getPersonalPM().findById(Page.class, name);
                if(personalPage!=null)
                    cachedPage = personalPage;     
            }
        } catch (Exception e) {
            if(log.isErrorEnabled())
                log.error(this, e);
            context.addErrorMessage(this, e.toString());
            return null;
        }

        if (cachedPage == null) {
            context.addErrorMessage(this, "page " + name + " not found");
            return null;
        }

        if (cachedPage.getApplication() == null) {
            cachedPage.setApplication(getApplicationName());
        }
        
        //security check
        if (!cachedPage.isViewable(this)) {
            deniedPage = cachedPage.getName();
            if(log.isErrorEnabled())
                log.error("Failed to access page "+deniedPage);
            throw new PageSecurityException("Page Access denied " + cachedPage.getName());
        }

        //
        //we do not cache at all if it is in design mode, so all the changes
        //are made to the original
        //page level lock can be implemented for multpile access
        //
        if (!isRunTime()) {
            if (cachedPage.isDesignable()) {
                pageCache.remove(HASHKEY_PREFIX + cachedPage.getApplication() + cachedPage.getId());
                if (!embedded) {
                    setPageInDesign(cachedPage);
                }
                return cachedPage;
            }
        }

        //get session level cached page
        Page page = pageCache.get(HASHKEY_PREFIX + cachedPage.getApplication() + name);
        //
        //if not cached, or cached but not compatible, defined by
        //equal, we will removed cached one 
        //
        if ((page == null) || !cachedPage.equals(page)) {
            if (page != null) {
                pageCache.remove(HASHKEY_PREFIX + cachedPage.getApplication() + page.getId());
            }

            if (log.isDebugEnabled()) {
                log.debug("Missed sesion level cache, and recache for the session");
            }

            //clone the reqion from backRegion, and cache it
            page = (Page) cachedPage.clone(context, false, false); //per session
            pageCache.put(HASHKEY_PREFIX + cachedPage.getApplication() + page.getId(), page);
        } else {
            if (log.isDebugEnabled()) {
                log.debug("Hit sesion level cache, loaded from cache");
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("Done getPage " + this.getClass().getName());
        }

        return page;
    }

    public void setPageFrom(String pageFrom) {
        this.pageFrom = pageFrom;
    }

    public String getPageFrom() {
        return pageFrom;
    }

    public void setPageInDesign(Page page) {
        pageInDesign = page;
    }

    /**
     * Returns list of permissions for an user
     *
     * @return DOCUMENT ME!
     */
    public List getPermissions() {
        return Collections.EMPTY_LIST;
    }

    /**
     * Returns a directory where personal information is stored.
     * Content in this directory can not be accessed directly from
     * web
     *
     * @return absolute location for the session user.
     *  For unauthorized user, a common directory is returned.
     */
    public String getPersonalContentTop() {
        if ((sessionUser != null) && isAuthenticated()) {
            return config.getUserContentTop() + File.separatorChar + sessionUser.getId();
        } else {
            return config.getUserContentTop() + File.separatorChar + "tmp";
        }
    }

    /**
     * Returns a directory where personal web resource is stored.
     *
     * @return absolute location for the session user.
     *  For unauthorized user, a common directory is returned.
     */
    public String getPersonalWebTop() {
        if ((getUser() != null) && isAuthenticated()) {
            return config.getWebTop() + File.separatorChar + "users" + File.separatorChar +
            getUser().getId();
        } else {
            return null;
        }
    }

    /**
     * Toggles the design mode.
     *
     * @param runTime
     */
    public void setRunTime(boolean runTime) {
        if (this.runTime != runTime) {
            this.runTime = runTime;

            if (runTime) {
                clipboard.clear();
                this.selected=null;
            }
        }
    }

    /**
     * Returns true if  session is not in design mode.
     *
     * @return return-value
     */
    public boolean isRunTime() {
        return runTime || !isAuthenticated();
    }

    public SecurityManager getSecurityManager() {
        try {
            return ((ApplicationImpl) ApplicationFactoryImpl.getFacesApplication()).getSecurityManager();
        } catch (Exception e) {
            if(log.isErrorEnabled())
                log.error(this, e);
            return new DummySecurityManager();
        }
    }

    /**
     * Sets select to session clipboard
     *
     * @param selected component!
     */
    public void setSelected(UIComponentImpl selected) {
        this.selected = selected;
        if(selected!=null)
            selected._onSelected();
    }

    /**
     * Returns selected component.
     *
     * @return selected component
     */
    public UIComponent getSelected() {
        return selected;
    }

    /**
     * Returns true if the current client session times out
     *
     * @return result of testing.
     */
    public boolean isSessionExpired() {
        return sessionState == SESSION_EXPIRED;
    }
    /**
     * Sets the flag to show managed beans.  Managed beans are ones that 
     * added to page from variable component tab.
     * @param showMB the flag indicates whether to show managed beans.
     */
    public void setShowManagedBean(boolean showMB) {
        this.showMB = showMB;
    }
    /**
     * Flag whether to show the managed been for this session.
     * @return false if managed beans are not shown.
     */
    public boolean isShowManagedBean() {
        return showMB;
    }

    public String getStatus() {
        return "Seleced " + getApplication();
    }

    /**
     * Returns current time, in localized string;
     *
     * @return the date of current server time.
     */
    public Date getTime() {
        return Calendar.getInstance(locale).getTime();
    }
    
    /**
     * Returns a session local toolbar. It clones from the template, and returns
     * to the caller.
     * @param ctx
     * @return
     */
    public Page getToolBar(WebContext ctx) {
        //application level component
        //construct new page, this is a lazy loading thing,
        //and only one per page for
        //entire application
        if ((toolBar == null) && !isRunTime()) {
            Page backRegion = (Page) RootContainer.findBeanz(ctx, ApplicationImpl.TOOL_BAR, Page.class);

            if (backRegion == null) {
                ctx.addErrorMessage(this, "Can not load tool bar. Likely loader is not suitable");
                return null;
            }

            toolBar = (Page) backRegion.clone(ctx, false, false); //per session 
            if (toolBar != null) {
                toolBar.setDesignable(false);
                Registry.registerComponents(toolBar);
            }
        }
        return toolBar;
    }

    /**
     * Returns current session user.
     * @return current session user.
     * */
    
    public SessionUser getUser() {
        return sessionUser;
    }

    /**
     * Adds a session variable. This value is not added to HttpSession.
     *
     * @param key parameter
     * @param object parameter
     */
    public void addAttribute(String key, Object object) {
        attributes.put(key, object);
    }

    public Map getAttributes() {
        return attributes;
    }
    
    public void addOnLoginListener(EventListener listener) {
    if(!onLoginListeners.contains(listener))
        this.onLoginListeners.add(listener);
    }

    public void addOnLogoutListener(EventListener listener) {
        if(!onLogoutListeners.contains(listener))
        this.onLogoutListeners.add(listener);
    }

    public boolean isTimeOut() {
        return System.currentTimeMillis() - dateVisitedLastTime >config.getSessionTimeout();
    }
    
    public void validate() {
        if (bAuthenticated && isTimeOut()) {
            sessionState = SESSION_EXPIRED;
            logout();
        }else {
            dateVisitedLastTime = System.currentTimeMillis();
        }
    }

    public void bindContext(WebContext ctx) {
        if (!bAuthenticated) {
            Cookie user = (Cookie) ctx.getCookies().get(USERNAME);
            Cookie loginStatus = (Cookie) ctx.getCookies().get(LOGGED_IN);

            if ((user != null) && (loginStatus != null) && "true".equals(loginStatus.getValue())) {
                if (!performAutoAuthenticate(ctx, user.getValue())) {
                    ctx.removeCookie(USERNAME);
                    ctx.removeCookie(LOGGED_IN);
                }
            }
        }
        setContext(ctx);
        this.applicationImpl =(ApplicationImpl) ctx.getApplication();
        ctx.setSession(this);
        if(browserInfo!=null )
        { 
            browserInfo = new BrowserInfo(ctx.getRequest());
        }
        
    }

    public boolean hasSeleced() {
        return selected != null;
    }

    /**
     * Invalidate current user as the session user. Session user become a guest user
     */
    public void logout() {
        if (log.isDebugEnabled()) {
            log.debug("Start logout " + this.getClass().getName());
        }

        setRunTime(true);

        SessionEvent event = new SessionEvent(this);

        for (int i = 0; i < onLogoutListeners.size(); i++) {
            SessionListener listener = (SessionListener) onLogoutListeners.get(i);
            listener.onLogout(context, event);
        }

        invalidateSession();
        sessionState = SESSION_INVALID;
        bAuthenticated = false;
        setUser(getSecurityManager().createGuestUser());

        onLogoutListeners.clear();
        onLoginListeners.clear();

        if (context != null) {
            context.removeCookie(LOGGED_IN);
            context.setRedirectPage(context.getPageName() + ".xsp");
        }

        if (log.isDebugEnabled()) {
            log.debug("Done logout " + this.getClass().getName());
        }
    }

    /**
     * Using default authenticator
     *
     * @param ctx
     * @param userName
     * @param password
     *
     * @return
     */
    public boolean performAuthenticate(WebContext ctx, String userName, String password) {
        ctx.removeCookie(LOGGED_IN);
        boolean result = authenticate(ctx, userName, password, getSecurityManager());

        if (result) {
            ctx.addCookie(USERNAME, userName);
            ctx.addCookie(LOGGED_IN, "true");
        }

        return result;
    }

    public void removeAttribute(String key) {
        attributes.remove(key);
    }

    /**
    * Forcefully remove cached page.
    * @param page
    */
    public void removeCachedPage(Page page) {
        pageCache.remove(HASHKEY_PREFIX + page.getApplication() + page.getId());
    }

    public void updateLocale() {
        if (sessionUser != null) {
            locale = new Locale(sessionUser.getLanguage(), sessionUser.getCountry());
        } else {
            locale = Locale.getDefault();
        }
    }

    //servlet session bound since this is addred to http session, and 
    //implement bindinglistener interface.

    /**
     * Called when session is bound to a client session.
     *
     * @param event
     */
    public void valueBound(HttpSessionBindingEvent event) {
    }

    //sevlet session unbound

    /**
     * Called when session is unbound to a client session. Usually caused by timeout.
     *
     * @param event
     */
    public void valueUnbound(HttpSessionBindingEvent event) {
        invalidateSession();
    }

    private void setUser(SessionUser pUser) {
        sessionUser = pUser;

        try {
            updateLocale();
            dateVisitedLastTime = System.currentTimeMillis();
        } catch (Exception e) {
            if(log.isErrorEnabled())
                log.error(this, e);
        }
    }

    //create a new session
    private void init() {
        runTime = true;
        locale = Locale.getDefault();
        //
        personalManager = null;
        bAuthenticated = false;
        //starts timer
        dateVisitedLastTime = System.currentTimeMillis();
        //create default session user
        setUser(getSecurityManager().createGuestUser());
    }
    //called per session user change
    private void initializeSession() {
        if (log.isDebugEnabled()) {
            log.debug("start initializeSession");
        }

        if (pageCache != null) {
            pageCache.clear();
        }

        selected = null;
        contentOwner = null;

        //clear clipboard
        clipboard.clear();

        //clear attributes
        attributes.clear();

        Iterator iter=config.getBeans().iterator();
        while(iter.hasNext()) {
            ManagedBeanDef mbd= (ManagedBeanDef) iter.next();
            if("session".equals(mbd.getScope())) {
                Object obj = mbd.createInstance();
                if(obj!=null)
                    this.addAttribute(mbd.getName(),obj);
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("done initializeSession");
        }
    }

    private void invalidateSession() {
        if (log.isDebugEnabled()) {
            log.debug("start invalidateSession");
        }

        getSecurityManager().logout(this.context, sessionUser);
        initializeSession();

        if (log.isDebugEnabled()) {
            log.debug("done invalidateSession");
        }
    }

    private boolean authenticate(WebContext ctx, String userName, String password, Authenticator authenticator) {
        //
        //if username is the same as current session user,
        //do not reauthenticate
        //
        if (bAuthenticated && !userName.equals(getUser().getUserName())) {
            return true;
        }

        // login as a differnt user, so logout as current user
        if (bAuthenticated) {
            sessionState = SESSION_EXPIRED;
            logout();
        }

        SessionUser user = authenticator.login(ctx, userName, password.toCharArray());

        //authenticat the new user
        bAuthenticated = user != null;

        //if succeed, 
        if (bAuthenticated) {
            initializeSession();
            setUser(user);
            dateVisitedLastTime = System.currentTimeMillis();
            sessionState = SESSION_VALID;

            //context.getRespondTree()
            // call login listeners if there are any
            SessionEvent event = new SessionEvent(this);

            for (int i = 0; i < onLoginListeners.size(); i++) {
                SessionListener listener = (SessionListener) onLoginListeners.get(i);
                listener.onLogin(context, event);
            }
        }
        return bAuthenticated;
    }
    //this method is called to authenticate a user via cookie values
    //we do not store password on the client machine
    //so authentication will be done without password
    private boolean performAutoAuthenticate(WebContext ctx, String userName) {
        SessionUser user = (getSecurityManager()).login(ctx, userName, null);
        bAuthenticated = user != null;
        //if succeed, 
        if (bAuthenticated) {
            setUser(user);
            initializeSession();
        }
        return bAuthenticated;
    }
    /**
     * Returns a page was denied. This is used when an authorized user to access 
     * a private page. The user is directed to the login page, and if success,
     * this page will be shown to the user.
     * @return the page name the session user was denied.
     */
    public String getDeniedPage() {
        return deniedPage;
    }

    public BrowserInfo getBrowserInfo() {
        return browserInfo;
    }
}

//
